package com.link.base.wechat.payment.util;

import com.link.core.cllog.LCLogger;
import com.link.core.basic.service.ServiceException;
import com.link.core.util.DateUtil;
import com.link.base.core.basic.util.YmlConstants;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContexts;
import org.apache.http.util.EntityUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;

import javax.net.ssl.SSLContext;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.MessageDigest;
import java.util.*;

/**
 * 支付工具类
 *
 * @author LiQun
 * @date 2019/2/25 19:49
 */
public class PayUtils {

    /**
     * 签名类型
     *
     * @author LiQun
     * @date 2019/2/25 20:06
     */
    public enum SignType {
        /**
         * MD5类型
         */
        MD5,
        /**
         * HMACSHA256类型
         */
        HMACSHA256
    }

    /**
     * 是否强校验签名
     *
     * @author LiQun
     * @date 2019/2/27 10:54
     */
    public enum ForceCheck {
        /**
         * 不校验真实姓名
         */
        NO_CHECK,
        /**
         * 强校验真实姓名
         */
        FORCE_CHECK
    }

    /**
     * 统一下单url
     */
    public static final String PAY_UNIFIED_ORDER_URL = "https://api.mch.weixin.qq.com/pay/unifiedorder";

    /**
     * 企业付款
     */
    public static final String MP_MCHPAY_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers";

    /**
     * 日期格式 yyyyMMddHHmmss
     */
    private static final String TIME_FORMATTER =  "yyyyMMddHHmmss";


    /**
     * 生成随机数
     */
    public static String createNonceStr() {
        String currTime = PayUtils.getCurrTime();
        //8位日期
        String strTime = currTime.substring(8, currTime.length());
        //四位随机数
        String strRandom = PayUtils.buildRandom(4) + "";
        return strTime + strRandom;
    }

    /**
     * 创建md5摘要,规则是:按参数名称a-z排序,遇到空值的参数不参加签名。
     *
     * @author 路江
     * @param packageParams 交易对象
     * @param key api秘钥 为商户平台设置的密钥key
     * @return String
     * @throws Exception
     */
    public static String createSign(SortedMap packageParams, String key) throws Exception {
        // 根据交易对象中字段,创建k-v结构
        StringBuilder sb = new StringBuilder();
        Set es = packageParams.entrySet();
        for (Object e : es) {
            Map.Entry entry = (Map.Entry) e;
            String k = (String) entry.getKey();
            String v = (String) entry.getValue();
            if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) {
                sb.append(k).append("=").append(v).append("&");
            }
        }
        sb.append("key=").append(key);
        return md5Encode(sb.toString()).toUpperCase();
    }

    /**
     * MD5签名
     *
     * @author 路江
     * @param inStr
     * @return String
     * @throws Exception
     */
    public static String md5Encode(String inStr) throws Exception {
        MessageDigest md5 = null;
        try {
            md5 = MessageDigest.getInstance("MD5");
        } catch (Exception e) {
            LCLogger.withException(e);
            return "";
        }

        byte[] byteArray = inStr.getBytes(StandardCharsets.UTF_8);
        byte[] md5Bytes = md5.digest(byteArray);
        StringBuilder hexValue = new StringBuilder();
        for (byte md5Byte : md5Bytes) {
            int val = ((int) md5Byte) & 0xff;
            if (val < 16) {
                hexValue.append("0");
            }
            hexValue.append(Integer.toHexString(val));
        }
        return hexValue.toString();
    }

    /**
     * 获取从1970年开始到现在的秒数
     *
     * @author 路江
     * @return
     */
    public static String getTimeStamp() {
        long seconds = System.currentTimeMillis() / 1000;
        return String.valueOf(seconds);
    }

    /**
     * 取出一个指定长度大小的随机正整数.
     *
     * @author 路江
     * @param length  int 设定所取出随机数的长度。length小于11
     * @return int 返回生成的随机数。
     */
    public static int buildRandom(int length) {
        int num = 1;
        Double n = 0.1;
        double random = Math.random();
        if (random < n) {
            random = random + n;
        }
        for (int i = 0; i < length; i++) {
            num = num * 10;
        }
        return (int) ((random * num));
    }

    /**
     * 获取当前时间 yyyyMMddHHmmss
     * @return String
     */
    public static String getCurrTime() {
        return DateUtil.dateToStr(new Date(), TIME_FORMATTER);
    }

    /**
     * 获得预支付订单号
     *
     * @author 路江
     * @param url 统一下单url
     * @param xmlParam 带参数的xml
     * @return
     */
    public static Map<String, Object> getPayNo(String url, String xmlParam) {
        Map<String, Object> map = new HashMap<>(16);
        try {
            String jsonStr = postWithXmlParams(url, xmlParam);
            map = doXMLParse(jsonStr);

        } catch (Exception e) {
            LCLogger.withException(e);
        }
        return map;
    }

    /**
     * 发送请求
     *
     * @author 路江
     * @param url 请求路径
     * @param xmlParams xml字符串
     * @return
     */
    public static String postWithXmlParams(String url, String xmlParams) {
        CloseableHttpClient httpClient = HttpClients.createDefault();
        HttpPost method = new HttpPost(url);
        try {
            method.setEntity(new StringEntity(xmlParams, "UTF-8"));
            HttpResponse response = httpClient.execute(method);
            return EntityUtils.toString(response.getEntity(), "UTF-8");
        } catch (Exception e) {
            LCLogger.withException(e);
            return "";
        }
    }

    /**
     * 解析xml,返回第一级元素键值对。如果第一级元素有子节点，则此节点的值是子节点的xml数据。
     *
     * @author 路江
     * @param strxml
     * @return Map
     * @throws Exception
     */
    public static Map<String,Object> doXMLParse(String strxml) throws Exception {
        Map<String,Object> map = new HashMap<>(16);
        Document doc = null;
        try {
            doc = DocumentHelper.parseText(strxml);
        } catch (DocumentException e) {
            LCLogger.withException(e);
        }
        if (doc == null) {
            return map;
        }
        Element root = doc.getRootElement();
        for (Iterator iterator = root.elementIterator(); iterator.hasNext();) {
            Element e = (Element) iterator.next();
            map.put(e.getName(), e.getText());
        }
        return map;

    }


    /**
     * 获取本机ip
     *
     * @author 路江
     * @param request
     * @return
     */
    public static String getIpAddress(HttpServletRequest request) {
        // 避免反向代理不能获取真实地址, 取X-Forwarded-For中第一个非unknown的有效IP字符串
        String unknown = "unknown";
        String ip = request.getHeader("x-forwarded-for");
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getHeader("WL-Proxy-Client-IP");
        }
        if (ip == null || ip.length() == 0 || unknown.equalsIgnoreCase(ip)) {
            ip = request.getRemoteAddr();
        }
        return ip;
    }

    /**
     * 企业支付请求
     *
     * @author 路江
     * @param xml
     * @param url
     * @param mchId
     * @return Map
     * @throws Exception
     */
    public static Map<String, Object> post(String xml,String url,String mchId) throws Exception {
        Map<String, Object> map = new HashMap<>(16);
        CloseableHttpClient httpclient = null;
        CloseableHttpResponse response = null;
        InputStreamReader reader = null;
        try {
            String path = YmlConstants.getWxPayCertPath() + "apiclient_cert.p12";
            httpclient = certificateValidation(path,
                    mchId);
            HttpPost httppost = new HttpPost(url);
            StringEntity myEntity = new StringEntity(xml, "UTF-8");
            httppost.setEntity(myEntity);
            if (null != httpclient) {
                response = httpclient.execute(httppost);
            } else {
                throw new ServiceException("PAYMENT-013");
            }
            if (null != response) {
                HttpEntity resEntity = response.getEntity();
                reader = new InputStreamReader(
                        resEntity.getContent(), "UTF-8");
                char[] buff = new char[1024];
                int length = 0;
                StringBuffer strhuxml = new StringBuffer();
                while ((length = reader.read(buff)) != -1) {
                    strhuxml.append(new String(buff, 0, length));
                }
                // 解析传过来的xml
                map = doXMLParse(strhuxml.toString());
            } else {
                throw new ServiceException("PAYMENT-013");
            }
        } catch (Exception e) {
            LCLogger.info().withMessage("企业支付失败");
            LCLogger.withException(e);
        } finally {
            if (httpclient != null) {
                try {
                    httpclient.close();
                } catch (Exception e) {
                    LCLogger.withException(e);
                }
            }
            if (response != null) {
                try {
                    response.close();
                } catch (Exception e) {
                    LCLogger.withException(e);
                }
            }
            if (reader != null) {
                try {
                    reader.close();
                } catch (Exception e) {
                    LCLogger.withException(e);
                }
            }
        }
        return map;
    }

    /**
     * 根据签名文件,创建httpClient
     *
     * @author 路江
     * @date 2019/2/25 17:32
     */
    public static CloseableHttpClient certificateValidation(String zfpath,String mchid) throws Exception {
        // 指定读取证书格式为PKCS12
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        // 证书地址
        FileInputStream instream = new FileInputStream(new File(zfpath));
        try {
            keyStore.load(instream, mchid.toCharArray());
        } finally {
            instream.close();
        }
        // Trust own CA and all self-signed certs
        SSLContext sslcontext = SSLContexts.custom()
                .loadKeyMaterial(keyStore, mchid.toCharArray()).build();
        // Allow TLSv1 protocol only
        SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext);
        CloseableHttpClient httpclient = HttpClients.custom()
                .setSSLSocketFactory(sslsf).build();
        return httpclient;
    }

}
